Skip to content

Fix BOM consumer POM leaving property references unresolved - #12625

Merged
gnodet merged 1 commit into
apache:masterfrom
gnodet:fix/bom-consumer-pom-property-resolution
Jul 30, 2026
Merged

Fix BOM consumer POM leaving property references unresolved#12625
gnodet merged 1 commit into
apache:masterfrom
gnodet:fix/bom-consumer-pom-property-resolution

Conversation

@gnodet

@gnodet gnodet commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes the BOM consumer POM builder leaving ${...} property references unresolved when consumer POM flattening is disabled (the default).

Reported by Karl Heinz Marbaise during the 4.0.0-rc-6 vote.

Problem

DefaultConsumerPomBuilder.buildBomWithoutFlatten() used getRawModel() (no interpolation), but then transformBom() strips both the parent and properties sections. The result is a consumer POM with dangling property references that no consumer can resolve.

Example — a BOM with ${junit.version} defined in its parent produced this consumer POM:

<!-- No groupId, no version, no properties, no parent -->
<project>
  <modelVersion>4.0.0</modelVersion>
  <artifactId>bom</artifactId>
  <packaging>pom</packaging>
  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>${junit.version}</version>   <!-- unresolvable! -->
      </dependency>
    </dependencies>
  </dependencyManagement>
</project>

Scope of breakage

This affects virtually all real-world BOMs on the default (no-flatten) code path:

Scenario Broken?
Property-referenced dependency versions (${junit.version}) ✅ Yes — dangling ${...}
${project.version} in dependency versions ✅ Yes — dangling
CI-friendly ${revision} ✅ Yes — dangling
Inherited groupId / version from parent ✅ Yes — missing entirely
Standalone BOM with all literal versions, own groupId/version ❌ No — but this is extremely rare

Root cause

The buildBomWithoutFlatten() method (introduced in #11427) was modeled after buildPom() which also uses getRawModel(). But transformPom() preserves the parent reference and properties (so consumers can resolve ${...} by reading the parent), while transformBom() strips them (BOMs are self-contained). Using getRawModel() with a parent-stripping transform is fundamentally incompatible.

Fix

  • Route all BOMs through buildBom() which uses getEffectiveModel() (fully interpolated)
  • Delete the now-dead buildBomWithoutFlatten() method

The flatten flag has no semantic effect on BOMs because transformBom() always strips parent and properties to produce a self-contained POM, regardless of the flag. The distinction only matters for regular POM packaging (where no-flatten preserves the parent reference).

Before → After

Field Before (broken) After (fixed)
groupId missing org.apache.maven.its.bom-property
version missing 1.0.0-SNAPSHOT
junit version ${junit.version} 4.13.2

Test plan

  • New IT MavenITBomConsumerPomPropertyResolutionTest with 3 test methods:
    • testBomConsumerPomResolvesParentProperties — default (no-flatten) path
    • testBomConsumerPomWithFlattenResolvesParentProperties — flatten=true path
    • testConsumerPomInProjectLocalRepo — verifies the -consumer.pom artifact
  • All 4 existing BOM ITs pass (no regressions):
    • MavenITgh11427BomConsumerPomTest (2 tests)
    • MavenITConsumerPomBomFromSettingsRepoTest (1 test)
    • MavenITmng8293BomImportFromReactor (1 test)

🤖 Generated with Claude Code

The BOM consumer POM builder used getRawModel() (no interpolation)
when consumer POM flattening was disabled (the default). Since
transformBom() always strips both the parent and properties sections,
any ${...} property references in dependency versions became
dangling and unresolvable in the published consumer POM.

This affected virtually all real-world BOMs:
- Properties for dependency versions (e.g. ${junit.version})
- ${project.version} references
- CI-friendly ${revision} properties
- Inherited groupId/version (missing entirely in the consumer POM)

The fix removes buildBomWithoutFlatten() and routes all BOMs through
buildBom(), which uses getEffectiveModel() (fully interpolated).
The flatten flag has no semantic effect on BOMs because transformBom()
always produces a self-contained POM regardless.

Reported-by: Karl Heinz Marbaise (during the 4.0.0-rc-6 vote)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Clean, well-motivated bugfix for a real and severe issue (dangling ${...} in BOM consumer POMs) reported during the Maven 4.0.0-rc-6 vote. The two-line code change is correct, the dead-code removal is safe (no subclasses, no other callers), and the new integration test provides thorough coverage of the fix.

Details:

  • The root-cause analysis is accurate: buildBomWithoutFlatten() used getRawModel() (uninterpolated), and then transformBom() stripped parent and properties via prune(), leaving dangling ${...} references. Routing all BOMs through buildBom() (which uses getEffectiveModel()) is the correct fix.
  • The removed buildBomWithoutFlatten() method has no subclass overrides and only one call site, so the removal is safe.
  • The new integration test covers all three relevant paths: default/no-flatten, flatten=true, and project-local-repo consumer POM.
  • The existing test did not catch this bug because it only checked packaging and <dependencyManagement>, not property resolution. The new test fills that gap.

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

Claude Code on behalf of gnodet

@gnodet
gnodet merged commit 10890c9 into apache:master Jul 30, 2026
22 checks passed
@github-actions

Copy link
Copy Markdown

@gnodet Please assign appropriate label to PR according to the type of change.

@github-actions github-actions Bot added this to the 4.1.0 milestone Jul 30, 2026
gnodet added a commit that referenced this pull request Jul 30, 2026
…es unresolved

Cherry-pick of #12625. Route all BOMs through buildBom() which uses
getEffectiveModel() instead of buildBomWithoutFlatten() which used
getRawModel(). The latter was incompatible with transformBom() which
strips parent and properties, leaving dangling ${...} references.

Closes #12627
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant